package uc.protocols;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import uc.DCClient;
/**
* This class will handle in non blocking io fashion every io
* from all running sockets..
*
* will also give possibility for the protocols to retrieve a socket (and unregister it..)
* that will be for example needed if Compression or sth alike must be done..
*
*
*
* @author Quicksilver
*
*/
public class MultiStandardConnection implements Runnable {
private static final Logger logger = LoggerFactory.make();
private static MultiStandardConnection msc = null;
private final Queue<Runnable> executeingQueue = new LinkedList<Runnable>();
private final ReentrantLock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
// private final Object synch = new Object();
//private HashMap<ConnectionProtocol,SelectionKey> protocolToKey = new HashMap<ConnectionProtocol,SelectionKey>();
private Selector selector = null;
/**
*
* @return the singleton MultiStandardConnection, creates one if none exists..
*/
public synchronized static MultiStandardConnection get() {
logger.debug("in get()");
if (msc == null) {
msc = new MultiStandardConnection();
msc.start();
try {
//wait for selector getting set
synchronized(msc) {
while (msc.selector == null) {
msc.wait(100);
}
}
} catch(InterruptedException ie) {}
}
logger.debug("end of get");
return msc;
}
private MultiStandardConnection(){} // only private constructor..
private void start() {
Thread t = new Thread(this,"IO-Thread");
t.setDaemon(true);
t.start();
}
/**
*
* @param sc - socketChannel needing registering
* @param ucp - the object managing the connection
* @param reregister - if reregister is true then socket won't be checked for connecting
*/
public void register(final SocketChannel sc, final IUnblocking ucp,final boolean reregister) {
logger.debug("registering");
synchExec(new Runnable() {
public void run() {
try {
final boolean pending = !reregister && sc.isConnectionPending();
SelectionKey key = sc.register(selector,
pending ? SelectionKey.OP_CONNECT
: SelectionKey.OP_READ, ucp);
ucp.setKey(key);
//ucp.key = key;
if (!reregister && !pending) { //workaround.. if the channel has already completed connection when we arrive here..
logger.debug("shortcut taken");
ucp.connected();
}
} catch(ClosedChannelException cce) {
logger.debug(cce, cce);
}
}
});
}
public void register(final ServerSocketChannel ssc, final ISocketReceiver ucp) {
logger.debug("registering serversocket");
asynchExec(new Runnable() {
public void run() {
try {
ssc.configureBlocking(false);
SelectionKey sel = ssc.register(selector, SelectionKey.OP_ACCEPT, ucp);
ucp.setKey(sel);
} catch(IOException ioe) {
logger.error(ioe,ioe);
}
}
});
}
public void synchExec(Runnable run) {
lock.lock();
try {
executeingQueue.offer(run);
do {
cond.awaitUninterruptibly();
} while(executeingQueue.contains(run));
} finally {
lock.unlock();
}
}
public void asynchExec(Runnable run) {
lock.lock();
try {
executeingQueue.offer(run);
} finally {
lock.unlock();
}
}
private void runSubmitted() {
lock.lock();
try {
Runnable run;
while (null != (run = executeingQueue.poll())) {
run.run();
}
cond.signalAll();
} finally {
lock.unlock();
}
}
public void run() {
try {
// Create the selector
synchronized(this) {
if (selector == null) {
selector = Selector.open();
}
notifyAll();
}
logger.debug("selector created");
// Wait for events
while (true) {
try {
// Wait for an event
selector.select(100);
} catch (IOException e) {
logger.error("selector error",e);
}
runSubmitted();
// Get list of selection keys with pending events
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
// Process each key at a time
while (it.hasNext()) {
// Get the selection key
SelectionKey selKey = it.next();
// Remove it from the list to indicate that it is being processed
it.remove();
try {
processSelectionKey(selKey);
} catch (IOException e) {
// Handle error with channel and unregister
if (selKey.attachment() instanceof IUnblocking) {
((IUnblocking )selKey.attachment()).onDisconnect();
} else {
logger.error("Exception received: "+e,e);
}
} catch(CancelledKeyException ce) {
//between isValid check and using the key could be invalidated..
if (selKey.attachment() instanceof IUnblocking) {
((IUnblocking )selKey.attachment()).onDisconnect();
} else {
logger.error("Exception received: "+ce,ce);
}
} catch(NotYetConnectedException nyce) {
if (selKey.attachment() instanceof IUnblocking) {
((IUnblocking )selKey.attachment()).onDisconnect();
} else {
logger.error("Exception received: "+nyce,nyce);
}
//this should not be caught... shows error on beginning connection..
}
}
}
} catch(Exception e) {
logger.warn("Selection error, IO-Thread died ... restarting IO-Thread",e);
start();
}
}
public void processSelectionKey(SelectionKey selKey) throws IOException {
// Since the ready operations are cumulative,
// need to check readiness for each operation
if (selKey.isValid() && selKey.isConnectable()) {
logger.debug("key is connectable");
// Get channel with connection request
SocketChannel sChannel = (SocketChannel)selKey.channel();
boolean success = sChannel.finishConnect();
if (!success) {
// An error occurred; handle it
// Unregister the channel with this selector
// notify disconnect..
logger.debug("no success in connection"+sChannel.isConnectionPending());
((IUnblocking)selKey.attachment()).onDisconnect();
} else {
selKey.interestOps(SelectionKey.OP_READ);
((IUnblocking)selKey.attachment()).connected();
}
}
if (selKey.isValid() && selKey.isReadable()) {
logger.debug("key is readable");
// Get channel with bytes to read
((IUnblocking)selKey.attachment()).read();
}
if (selKey.isValid() && selKey.isWritable()) {
logger.debug("key is writeable");
// Get channel that's ready for more bytes
((IUnblocking)selKey.attachment()).write();
}
if (selKey.isValid() && selKey.isAcceptable()) {
final ServerSocketChannel ssc = (ServerSocketChannel)selKey.channel();
final SocketChannel sc = ssc.accept();
final ISocketReceiver isr = (ISocketReceiver)selKey.attachment();
DCClient.execute(new Runnable() {
public void run() {
isr.socketReceived(ssc, sc);
}
});
}
}
public static interface IUnblocking extends IHasKey {
void connected();
void onDisconnect() throws IOException;
void read() throws IOException;
void write() throws IOException;
}
public static interface ISocketReceiver extends IHasKey {
/**
* called directly by IO thread.. must therefore return immediately..
*
* @param port - the port the SererSocketchannel had
* @param created - the socket that was created..
*/
void socketReceived(ServerSocketChannel port, SocketChannel created);
}
public static interface IHasKey {
void setKey(SelectionKey key);
}
}